package com.qf.elasticsearch.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qf.elasticsearch.pojo.Movie;
import org.apache.commons.collections.KeyValue;
import org.apache.commons.collections.keyvalue.DefaultKeyValue;
import org.apache.lucene.search.PhraseQuery;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.hibernate.validator.constraints.pl.REGON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@RestController
@RequestMapping("/movies")
public class MovieController {


    // 分页查询
    @RequestMapping("/page")
    public Object pageQuery(
            @RequestParam(required = false, defaultValue = "10") Integer size,
            @RequestParam(required = false, defaultValue = "1") Integer page) {

        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withPageable(PageRequest.of(page, size))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }


    // 单条件范围查询, 查询电影的上映日期在2016年到2018年间的所有电影
    @RequestMapping("/range")
    public Object rangeQuery() {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new RangeQueryBuilder("year").from(2016).to(2018))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    // 单条件查询只要包含其中一个字段
    @RequestMapping("/match")
    public Object singleCriteriaQuery(String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new MatchQueryBuilder("title", searchText))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    // 多条件并且分页查询
    @RequestMapping("/match/multiple")
    public Object multiplePageQuery(
            @RequestParam(required = true) String searchText,
            @RequestParam(required = false, defaultValue = "10") Integer size,
            @RequestParam(required = false, defaultValue = "1") Integer page) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(
                        new BoolQueryBuilder()
                                .must(new MatchQueryBuilder("title", searchText))
                                .must(new RangeQueryBuilder("year").from(2016).to(2018))
                ).withPageable(PageRequest.of(page, size))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    // 多条件并且分页查询
    @RequestMapping("/match/or/multiple")
    public Object multipleOrQuery(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(
                        new BoolQueryBuilder()
                                .should(new MatchQueryBuilder("title", searchText))
                                .should(new RangeQueryBuilder("year").from(2016).to(2018))
                ).build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    //其中包含有某个给定单词，必须是一个词
    @RequestMapping("/term")
    public Object termQuery(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new TermQueryBuilder("title", searchText)).build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    //其中包含有某个几个单词
    @RequestMapping("/terms")
    public Object termsQuery(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new TermsQueryBuilder("title", searchText.split("\\s+"))).build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    @RequestMapping("/multiple/field")
    public Object allTermsQuery(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new MultiMatchQueryBuilder(searchText, "title", "genre").type(MultiMatchQueryBuilder.Type.MOST_FIELDS))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    // 多单词同时包含
    @RequestMapping("/also/include")
    public Object alsoInclude(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new QueryStringQueryBuilder(searchText).field("title").defaultOperator(Operator.AND))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    @RequestMapping("/phrase")
    public Object phraseQuery(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new MatchPhraseQueryBuilder("title", searchText))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }

    @RequestMapping("/source")
    public Object sourceQuery(@RequestParam(required = true) String searchText) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withSourceFilter(new FetchSourceFilter(new String[]{"title", "year", "id"}, new String[]{}))
                .withQuery(new MatchPhraseQueryBuilder("title", searchText))
                .build();

        List<Movie> movies = elasticsearchTemplate.queryForList(searchQuery, Movie.class);

        return movies;
    }


//    @Resource
//    private ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;

//    @Resource
//    private MovieDao movieDao;

    @RequestMapping
    public Object getPageData() {
        /**
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new MatchAllQueryBuilder().queryName("match_all"))
                .withPageable(PageRequest.of(1, 20))
                .build();
         */

        /**
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(
                        new BoolQueryBuilder().must(new TermsQueryBuilder("title", "beautiful", "mind"))
                                              .must(new RangeQueryBuilder("year").from(2007).to(2013))
                ).withPageable(PageRequest.of(1, 10))
                .build();
        */
        /**  */
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withSourceFilter(new FetchSourceFilter(new String[]{"year", "id", "title", "genre"}, new String[]{"@version"}))
                .withQuery(new MatchQueryBuilder("title", "beautiful mind"))
                .withIndices("movies")
                .withHighlightFields(new HighlightBuilder.Field("title").preTags("<font color='red'>").postTags("</font>"))
                .withPageable(PageRequest.of(2, 20))
                .build();


//         return elasticsearchTemplate.query(searchQuery, new ResultsExtractor<Object>() {
//
//             @Override
//             public Object extract(SearchResponse response) {
//                 return response.getHits();
//             }
//         });

        return elasticsearchTemplate.query(searchQuery, new CustomResultExtractor());


        /**
        TermSuggestionBuilder termSuggestionBuilder = new TermSuggestionBuilder("title").text("beautiful mind")
                .suggestMode(TermSuggestionBuilder.SuggestMode.MISSING).size(10);

        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("query_movies", termSuggestionBuilder);


        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient()
                .prepareSearch("movies").suggest(suggestBuilder)
                .setQuery(new MatchQueryBuilder("title", "Beautiful Mind"));  //
//                .highlighter(new HighlightBuilder(new HighlightBuilder().preTags("<font color='red'>").postTags("</font>"),
//                        new MatchQueryBuilder("title", "Beautiful Mind"), Arrays.asList(new HighlightBuilder.Field("title"))));

        SearchHit[] searchHits = searchRequestBuilder.get().getHits().getHits();


        return searchHits;
         */

//        return movieDao.suggestQuery("beautiful mind");
    }
}

class CustomResultExtractor implements ResultsExtractor<List<MyClass>> {

    private ObjectMapper objectMapper;

    public CustomResultExtractor() {
        objectMapper = new ObjectMapper();
    }


    @Override
    public List<MyClass> extract(SearchResponse response) {
        return StreamSupport.stream(response.getHits().spliterator(), false)
                .map(this::searchHitToMyClass)
                .collect(Collectors.toList());
    }

    private MyClass searchHitToMyClass(SearchHit searchHit) {
        Movie myObject;
        try {
            myObject = objectMapper.readValue(searchHit.getSourceAsString(), Movie.class);
        } catch (IOException e) {
            throw new ElasticsearchException("failed to map source [ " + searchHit.getSourceAsString() + "] to class " + Movie.class.getSimpleName(), e);
        }
        List<String> highlights = searchHit.getHighlightFields().values()
                .stream()
                .flatMap(highlightField -> Arrays.stream(highlightField.fragments()))
                .map(Text::string)
                .collect(Collectors.toList());
        // Or whatever you want to do with the highlights

        return new MyClass(myObject, highlights);
    }
}

class MyClass {
    private Movie movie;
    private List<String> highLights;

    public MyClass() {
    }

    public MyClass(Movie movie, List<String> highLights) {
        this.movie = movie;
        this.highLights = highLights;
    }

    public Movie getMovie() {
        return movie;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
    }

    public List<String> getHighLights() {
        return highLights;
    }

    public void setHighLights(List<String> highLights) {
        this.highLights = highLights;
    }
}
